package study.springcloud.gateway.support.route;

import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 实现动态路由配置，主要需要做到2点：
 * （1）Nacos 能够监听到客户端前台配置文件的修改
 * （2）将修改的配置文件变更新到网关路由中
 */
@Slf4j
public class NacosDynamicRouteConfig implements ApplicationEventPublisherAware {

    private static final List<String> ROUTE_LIST = new ArrayList<>();

    @Value("${spring.cloud.nacos.config.server-addr}")
    private String serverAddr;
    @Value("${nacos.group}")
    private String group;
    @Value("${nacos.dataId}")
    private String routeConfigDataId;
    @Value("${nacos.namespace}")
    private String namespace;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @PostConstruct
    public void dynamicRouteListener() {
        try {
            Properties properties = new Properties();
            properties.put("serverAddr", serverAddr);
            properties.put("namespace", namespace);

            ConfigService configService = NacosFactory.createConfigService(properties);
            String initConfigInfo = configService.getConfig(routeConfigDataId, group, 5000);
            addAndPublishBatchRoute(initConfigInfo);

            configService.addListener(routeConfigDataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    addAndPublishBatchRoute(configInfo);
                }

                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
        } catch (Exception ex) {
            log.error("", ex);
        }
    }

    /**
     * 批量添加及发布路由
     */
    private void addAndPublishBatchRoute(String configInfo) {
        clearRoute();
        List<RouteDefinition> routeDefinitionLt = JSONUtil.toBean(configInfo, List.class);
        for (RouteDefinition routeDefinition : routeDefinitionLt) {
            addRoute(routeDefinition);
        }
        publish();
//        log.info(">>>>>>>>>>>>>>>>>Nacos Dynamic config gateway route finished | {}", JSON.toJSONString(gatewayRouteDefinitions));
    }

    private void clearRoute() {
        for (String id : ROUTE_LIST) {
            this.routeDefinitionWriter.delete(Mono.just(id))
                    .subscribe();
        }
        ROUTE_LIST.clear();
    }

    /**
     * 添加单条路由信息
     */
    private void addRoute(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition))
                .subscribe();
        ROUTE_LIST.add(definition.getId());
    }

    /**
     * 发布路由表变更事件
     */
    private void publish() {
        RefreshRoutesEvent event = new RefreshRoutesEvent(this.routeDefinitionWriter);
        this.publisher.publishEvent(event);
    }
}
